#ifndef MESSAGE_QUEUE_H_INCLUDED
#define MESSAGE_QUEUE_H_INCLUDED

/*
* Copyright (c) 2010 Michael Collins
*
* This file is part of TerraFirma.
*
* TerraFirma is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TerraFirma is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with TerraFirma.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <boost/thread/thread.hpp>
#include <queue>
#include "message_header.h"
#include <string.h>

/**
 * thread-safe medium for passing messages between threads and tasks
 * template based upon message type
 */
namespace engine
{
    template<typename T = message_header>
    class message_queue
    {
    public:
        message_queue(): empty(true) {}
        virtual ~message_queue()
        {
            while (!empty) pop();
        }

        //Casting operator to message_queue template type.
        //This is for mpi's benefit
        operator message_queue<>()
        {
            return *reinterpret_cast<message_queue<>*>(this);
        }

        message_queue<>* ref()
        {
            return reinterpret_cast<message_queue<>*>(this);
        }

        bool isempty() const
        {
            return empty;
        }

        const T& front() const
        {
            return queue.front();
        }

        void pop()
        {
            if (!empty)
            {
                boost::lock_guard<boost::mutex> lock(mut);
                queue.pop();
                empty = queue.empty();
            }
        }

        boost::condition_variable read; //Is this safe in public space?

    protected:
        friend class mpi; //Only mpi is allowed to enqueue messages for safety sake

        //The following 2 methods are virtual so that it does the correct thing
        //even when cast to the base message type (message_header)
        virtual void enqueue(const message_header* msg)
        {
            //Recast the message to the actual type we want
            const T* cast_msg = reinterpret_cast<const T*> (msg);

            //Lock the mutex via a unique lock so that should an exception occur, mutex is unlocked
            //(which is good because this method is basically ASKING for a segfault)
            {
                boost::lock_guard<boost::mutex> lock(mut);
                queue.push(*cast_msg);
                empty = false;
            } //Lock is freed here
            //Signal any threads waiting for the queue to have a message that a message is available
            read.notify_one();
        }

        //This simply enqueues a simple message header with no payload
        virtual void enqueue_header(const message_header* msg)
        {
            T full_msg(*msg); //This is to prevent memory access errors
            //memcpy(&full_msg, msg, sizeof(message_header));
            {
                boost::lock_guard<boost::mutex> lock(mut);
                queue.push(full_msg);
                empty = false;
            } //Lock is freed here
            //Signal any threads waiting for the queue to have a message that a message is available
            read.notify_one();
        }

        boost::mutex mut; //Queue guard
        std::queue<T> queue;
        bool empty;
    };

}

#endif // MESSAGE_QUEUE_H_INCLUDED
